\chapter{元对象系统}

Qt 的元对象系统提供了对象间通信的信号槽机制、运行时类型信息，以及动态属性系统。

元对象系统基于以下三者：

\begin{compactenum}
\item QObject 类，提供了便于利用元对象系统的基类；
\item Q\_OBJECT 宏，放置于类声明的私有域，用于激活元对象系统特性，例如动态属性和信号槽；
\item 元对象编译器 (moc)，为每个 QObject 的子类提供实现元对象特性的代码生成。
\end{compactenum}

moc 工具读取 C++ 源文件，若在其中找到包含 Q\_OBJECT 宏的类声明，
则会创建另一个 C++ 源文件，并在其中填充用于实现元对象得的代码。
该生成的源文件需要通过 \#include' 包含至对应类的源文件，
或者更常见的是将其加入编译列表，并于对应类的实现一同链接。

\begin{compactitem}
\item QObject::metaObject() 返回该类对应的 元对象。
\item QMetaObject::className() 在运行时以字符串形式返回该类的类名，并且不需要依赖 C++ 编译器的运行时类型信息(RTTI)支持；
\item QObject::inherits() 函数返回该对象所属类型，是否派生自 QObject 继承树中的指定类型；
\item QObject::tr() 和 QObject::trUtf8() 为 国际化 支持提供字符串翻译；
\item QObject::setProperty() 和 QObject::property() 用于通过属性名称，动态设置和读取属性值；
\item QMetaObject::newInstance() 构建指定类的新实例。
\end{compactitem}

我们还可以对 QObject 进行 qobject\_cast() 操作，
该函数与标准 C++ 的 dynamic\_cast() 表现类似，
但优点时不需要 RTTI 支持，并且可以跨越动态库边界运作。
它会尝试将输入指针转换为尖括号中的指针类型，
若类型正确则返回非空指针（在运行时作出判断），
若对象类型不兼容则返回 nullptr。

例如，假设 MyWidget 继承自 QWidget 类，并声明了 Q\_OBJECT 宏：

\begin{cppcode}
QObject *obj = new MyWidget;
\end{cppcode}

QObject * 类型的变量 obj 实际指向一个 MyWidget 对象，于是我们可以进行如下转换：

\begin{cppcode}
QWidget *widget = qobject_cast<QWidget *>(obj);
\end{cppcode}

从 QObject 到 QWidget 的转换成功进行，
因为该对象实际是 QWidget 的子类 MyWidget。
由于我们知道 obj 是 MyWidget 类型，
我们可以将其转换为 MyWidget *：

\begin{cppcode}
MyWidget *myWidget = qobject_cast<MyWidget *>(obj);
\end{cppcode}

转换至 MyWidget 的操作可以成功进行，因为 qobject\_cast() 并不会将 Qt 内置类型和自定义类型区别对待。
（译者注：Qt 是在运行时通过读取元对象信息进行动态转换，开发者可通过 Q\_OBJECT 宏让自定义类型支持被 qobject\_cast() 进行转换）

\begin{cppcode}
QLabel *label = qobject_cast<QLabel *>(obj);
// label is 0
\end{cppcode}

另一个例子，转换为 QLabel 的操作失败了，该指针会被置零。但此机制也让运行时基于转换结果来区别处理不同类型成为可能：

\begin{cppcode}
if (QLabel *label = qobject_cast<QLabel *>(obj)) {
    label->setText(tr("Ping"));
} else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) {
    button->setText(tr("Pong!"));
}
\end{cppcode}

虽然可以使用 QObject 作为基类，
但不定义 Q\_OBJECT 宏，也不生成元对象代码，
但这也意味着信号槽以及本文提到所有的其它机制无法使用。
从元对象系统的视角来看，没有元对象代码的 QObject 的子类
（译者注：即未使用 Q\_OBJECT 宏）等价于它最近的一个包含元对象代码的父类，
这也意味着，例如，QMetaObject::className() 不会返回该类的类名，而是会返回父类的类名。

因此，我们强烈建议在所有 QObject 的子类中都使用 Q\_OBJECT 宏，
无论它们是否用到了信号槽和动态属性。

\begin{seeAlso}
QMetaObject，Qt 的属性系统 以及 信号与槽。
\end{seeAlso}